/**
 * 学生管理模块
 */
var debug = require('debug')('10studentsmanage:routes');
var express = require('express');
var ObjectID = require('mongodb').ObjectID;

var mongoUtil = require('../common/mongoUtil');
// console.log(mongoUtil)  {curd: }
var crud = mongoUtil.crud;
var router = express.Router();


// const GENDERRULE = { '1': '男', '2': '女'}
/**
 * 查询学生列表
 * 匹配http://students/
 */

router.get('/', function(req, res, next){
    res.redirect('/students/list');
})
/**
 * 查询学生列表（简易版）
 * http://students/list
 * router.get('/list')
 */
router.get('/list', function(req, res, next){
    var context = {}
    // 查询数据库
    crud(function(db){
        db.collection('students').find({}).toArray(function(err, docs){
            if (err){
                context.message = '数据库报错，请联系管理员';
                throw err;
            } else {
                debug('docs:', docs);  // [{id:5d, no:'001', name:'小明'},{},{}]
                // 处理下数据， 代码转字符串、时间格式化、拼url
                docs.forEach(function(stu, i){
                    if (docs[i].gender == '1'){
                        docs[i].gender = '男'; 
                    } else if (docs[i].gender == '2'){
                        docs[i].gender = '女';
                    } else {
                        docs[i].gender = '未选择';
                    }
                    // docs[i].gender = docs[i].gender=='1'?'男':'女'
                })

                // 上下文对象：context ，连接上文代码（后端代码）和下文代码（前端代码）的变量
                // res.render('students/list.ejs', {message:'', students: docs})
                context.message = '查询成功';
                context.students = docs;
                debug('context', context);
                res.render('students/list.ejs', context)
            }
        })
    });
})

/** 
 * 查询学生列表（分页原理版）
 * 思路： http://students/list2?pageno=1&pagenum=20
 *      后端接收参数，根据参数查询结果集，例如pageNo=2 pageNum20 ,返回数据的第21-40行。
 *      再查询总行数， 总行数/每页显示数=总页数。  返回结果集和总页数渲染到ejs模板。
 *      前端循环渲染数据到表格，循环总页数 构造a标签，一行分页的按钮，用户点击某一页就会携带pageNo。 
 * 分析：已知pageno和pagenum，如何取结果条目。
 *      mongodb的skip和limit函数 https://blog.csdn.net/xiangwangxiangwang/article/details/83311325 
 *      id包含时间戳可以排序  https://www.cnblogs.com/weilunhui/p/6861938.html
 *      mongodb .count总条目数量http://cw.hubwiz.com/card/c/543b2f3cf86387171814c026/1/1/12/
 * 
 *      分页参数规律，
 *      pageno=1 pagenum=10, 取 1-10；
 *             2         10，   11-20；
 *      startItem = (pageno-1)*pagenum
 *      所以，分页结果集db.collection.sort('_id': 1).skip(startItem).limit(pagenum)
 *      
 *      还需要传前端总条目数  db.collection.count() 除以 pagenum 等于 pageamount总页数
 *      parseInt(数据库25条/每页10条)+1 = 3页  
 *      注意逻辑bug parseInt(20条/每页10条)+1 = 3页 而实际只需2页会造成第3页空白。
 *      所以应该向上取证  Math.ceil(20/10)
 *               
 */
router.get('/list2', function(req, res, next){
    debug('学生列表页-分页版...');
    var context = {};
    // 获取当前页码和每页条数。如果没有当前页码默认第1页
    // 每页条数先临时写死值。
    var pageno = parseInt(req.query.pageno) || 1;
    context.pageno = pageno;
    var pagenum = 5;    // parseInt(req.query.pagenum) || 20;

    // 取某一页结果集
    crud(function(db){
        db.collection('students').find()
        .sort({'_id': 1})
        .skip((pageno-1)*pagenum)
        .limit(pagenum)
        .toArray(function(err, docs){
            if (err) throw err;
            debug('docs:', docs);
            context.students = docs;
        })
    })

    // 总条目
    crud(function(db){
        db.collection('students')
        .countDocuments(function(err, count){
            if (err) throw err;
            debug(count);
            
            context.pageamount = Math.ceil(count/pagenum);

            debug('context:', context);
            // todo 没有使用同步执行，可能这句话执行完students数据还没返回导致前端无法渲染变量
            res.render('students/list2.ejs', context);
        })
    })

    // 由于数据库db.collection.find()等方法异步，最后等res会在取到数据前执行
    // 在一个crud()函数中执行两条语句，因为封装时最后db.close()，所以导致另一条语句执行时出错。
    // 按照目前crud()整体是同步等，多个查询分成多个crud写，res.render放到最后一个函数中末尾。
    // js官方也发现异步回调导致的逻辑问题，新增语法promise async await。

    // debug('context:', context);
    // res.render('students/list2.ejs', context);
})

/**
 * todo
 * 查询学生列表（分页bootstrap插件版）
 * express分页插件 https://github.com/expressjs/express-paginate
 */
router.get('/list3')



/**
 * 添加一个学生
 * 匹配http://students/add
 */
router.get('/add', function(req, res, next){
    // 可以renderFile(xxx.html) 但相对路径容易出错，绝对路径每次手动拼
    // 既然已经用了ejs，和规定了模板文件夹views，那么所有都走res.render()

    // 建议views下根据大块逻辑新建子文件夹。结构views/students/add.ejs

    // (了解)路径写法
    // 因为app.js中set('views', 'myapp/views') 设置了模板文件夹，
    // render()的路径是接着 myapp/views后面写相对路径。
    // 错误写法1: students/add.html  框架认为后缀html是一种模板引擎，而没有
    //         叫html包，就会报 module not found。
    //         正确写法  students/add.ejs 或省略后缀 students/add
    // 错误路径2:  /students/add 。 
    //          路径中的第一个正斜杠的含义是"磁盘文件系统的根目录" ,
    //          linux系统  /home/yangzheng/vsCode/stuManage/views,
    //          windows系统其实也有，只是被省略了  \D:\vsCode\stuManage
    // 正确🙆‍♂️：render函数的路径是相对于views文件夹的，所以相对路径写法应该是
    // ./students/add 或省略写为 student/add 
    res.render('students/add');
})

/**
 * 添加一个学生 -用户提交表单
 */
// 不上传图片版
// router.post('/doAdd', function(req, res, next){
//     // 获取参数和初步处理
//     // 虽然req.body获取到到表单直接存入数据库就行，但一般需要处理下数据。
//     console.log('****', req.body);
//     var formParams = req.body;
//     var no = formParams.no ? formParams.no :'0000';
//     var name = formParams.name || '无名';
//     var age = parseInt(formParams.age);
//     var gender = parseInt(formParams.gender);
//     var phone = formParams.phone;

//     // 表单结构可能跟数据库期望的结构不同，可以在这里组织
//     var student = {
//         stuNo: no,  //
//         name: name,
//         age: age,
//         gender: gender,
//         phone: phone
//     }

//     // 写数据库
//     crud(function(db){
//         db.collection('students').insertOne(student, function(err, result){
//             if (err) throw err;
//             console.log('插入成功 行：', result.insertedCount);
//         })
//     });
//     res.render('students/add-result');
// })

// ⏫上传图片版
router.post('/doAdd', function(req, res, next){
    console.log('添加学生...')
    // 获取参数和初步处理
    // 虽然req.body获取到到表单直接存入数据库就行，但一般需要处理下数据。
    // 处理表单图片enctype=muiltypart类型的数据
    var multiparty = require('multiparty');
    var form = new multiparty.Form();
    // 设置上传目录
    form.uploadDir = 'public/images';   
    // 请求经过multiparty处理后从回调中取属性，图片二进制自动写入路径，把图片路径保存到数据库
    form.parse(req, function(err, fileds, files){
        if(err)throw err;
        debug('fileds:', fileds);
        debug('files:', files);

        var no = fileds.no[0] ? fileds.no[0] :'0000';
        var name = fileds.name[0] || '无名';
        var age = parseInt(fileds.age[0]);
        var gender = parseInt(fileds.gender[0]);
        var phone = fileds.phone[0];
        // public/images/xxx.jpg
        var avatarPath = files.avatar[0].path;
        var avatarPath = avatarPath.replace('public', '');

        // 表单结构可能跟数据库期望的结构不同，可以在这里组织
        var student = {
            stuNo: no,  //
            name: name,
            age: age,
            gender: gender,
            phone: phone,
            avatarPath: avatarPath
        }

        // 写数据库
        crud(function(db){
            db.collection('students').insertOne(student, function(err, result){
                if (err) throw err;
                console.log('插入成功 行：', result.insertedCount);
            })
        });
        res.render('students/add-result');
    })
    
    
})

// 上传图片手动思路：
// 前端 <input type='file'> 
// 图片不是文字，不应该经过utf8 base64等编码，应上传原始数据
//  <form enctype="muiltypart/form-data">
// 后端 req.files 获取字节，如果获取不到的借助multer等三方库。
// 用fs.write() 图片应该存储在项目文件夹下 projectroot/public/images/uploads/xxx头像.jpg
// 把图片存储路径 /images/uploads/xxx头像.jpg 存到数据库的头像路径属性下。
// 需要展示时，读数据库 把图片路径取出，根据静态访问url看需不需要拼下静态访问路径，
// 前段img标签src渲染图片路径。
// 需要修改 add.ejs /doAdd  /list  list.ejs
// 
// 现成三方包方案: https://www.jianshu.com/p/e8bb3babf5a5
// https://segmentfault.com/p/1210000017949880?utm_source=tag-newest



// 过滤查询学生列表
// 修改某一个学生信息
/**
 *  删除一个学生
 *  思路：前端 发起请求携带要删除的学生的id(因为id不重复)，后端接收参数id，并根据id删除数据库的那一行
 *  传参方式一：查询参数queryParams    students/delete?stuid=3d1fa&key=value
 *           场景，适合过滤查询参数多，根据名字、年龄等。
 *  方式二：动态路由   students/delete/3d1fa  或 students/3d1fa/delete
 *         不合适等场景，多个参数  delete/小明/13/90, 不太符合逻辑有歧义。
 *         适合的场景，restful风格的路由，适合url即资源位置。 /students/3d1fa 
 *                  搜索引擎更容易收录。
 */


// router.get('/delete', function(req, res, next){
//     var _id = req.query.id;
//     debug('id', _id);
router.get('/delete/:id', function(req, res, next){
    var _id = req.param('id');
    debug('id', _id);
    
    // 删除方法如这样写 deleteOne({'_id': _id}  。发现删除无效，没有报错
    // js内置的数据结构比较有限，mongodb的_id跟唯一主键、快速查询有关，使用的字段类型
    // 并不是简单的字符串，而是ObjectID类型，所以转换后才能根据_id查询。
    var ObjectID = require('mongodb').ObjectID;
    crud(function(db){
        db.collection('students').deleteOne({'_id': ObjectID(_id)}, function(err, result){
            if (err) throw err;
            console.log('删除影响的行数：', result.result.n);
            res.send('删除学生结果页');
        })
    });
})

router.all('/update/:id', function(req, res, next){
    // （推荐）像添加功能分成 /add /doAdd 两个路由写可以。
    // 也可以写到一个路由，判断客户端http请求类型走不同路由。
    // router.all() 接收所有http类型请求
    // router.get('', funciton(){}).post('', function(){})
    // req对象常用属性总结： 
    //      1）动态路由 www.xx.com/student/0001       req.param('id')
    //      2）get查询参数    www.com/student?age=13&name=ming  req.query.age
    //      3) post表单参数   <input  name="username">      req.body.username
    if (req.method == 'GET'){
        debug('进入更新学生视图函数...')
        var _id = req.param('id');
        debug('_id', _id);
        // 根据id把要修改的学生信息查出来，传到更新表单的页面，
        // 用户就可以在原信息的基础上修改，体验更好。
        // db.collection.find({}).toArray(f(err, docs)) // [{id:3ae, name:'xiaoming'}]
        // db.collection.findOne({}, f(err, doc)) // {id:3ae, name:'xiaoming'}
        crud(function(db){
            db.collection('students')
            .findOne({'_id': ObjectID(_id)}, function(err, doc){
                if (err) throw err;   // 异常抛错 可以写在中间件请求响应函数外层，用try catch
                debug('doc', doc);
                res.render('students/update.ejs', {student: doc});
            })  
        })
    } else if (req.method == 'POST'){
        debug('去更新学生信息...');
        var _id = req.param('id');
        debug('_id', _id);

        // 表单参数。 与前端页面input标签name保持一致。
        var no = req.body.no;
        var name = req.body.name;
        var age = parseInt(req.body.age);
        var gender = parseInt(req.body.gender);
        var phone = req.body.phone;
        // 参数验证   略。 assert.notNull(name)   message='缺少参数'
        // 拼要插入到数据库到更新后到对象，字段应该与数据库一致。
        var studentUpdate = {
            stuNo: no,
            name: name,
            age: age,
            gender: gender,
            phone: phone
        }
        debug('studentUpdate', studentUpdate);

        // 写数据库
        crud(function(db){
            db.collection('students').updateOne({'_id': ObjectID(_id)},
                {$set: studentUpdate},
                function(err, result){
                    if (err) throw err;
                    debug('result', result);
                    // err是严重错误，例如没有连接上数据库，更新了不允许重复的约束字段。所以还要判断下updateCount。
                    debug(`更新了${result.modifiedCount} 行`);  
                    var message = '';
                    if (result.updatedCount == 1){ message = '更新成功'}
                    else { message = '更新失败'}
                    res.send('更新完成 <a href="/students/list">查询学生列表</a>');
                    // res.render('', {message: message})
                }
            )
        })
    }
})

module.exports = router;